package com.zeyu.framework.tools.console.web;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zeyu.framework.core.common.mapper.JsonMapper;
import com.zeyu.framework.core.persistence.Page;
import com.zeyu.framework.core.web.BaseController;
import com.zeyu.framework.core.web.Result;
import com.zeyu.framework.tools.console.TunnelRequestService;
import com.zeyu.framework.tools.console.entity.BasicConfiguration;
import com.zeyu.framework.tools.console.entity.ConsoleParam;
import com.zeyu.framework.tools.console.entity.RDPConfiguration;
import com.zeyu.framework.tools.console.entity.SSHConfiguration;
import com.zeyu.framework.tools.console.entity.TelnetConfiguration;
import com.zeyu.framework.tools.console.entity.VNCConfiguration;
import com.zeyu.framework.tools.console.service.ConsoleParamService;
import com.zeyu.framework.tools.console.stream.StreamInterceptingTunnel;
import com.zeyu.framework.utils.FileUtils;
import com.zeyu.framework.utils.IdGen;
import com.zeyu.framework.utils.StringUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.guacamole.GuacamoleException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;

/**
 * console 控制台界面
 * Created by zeyuphoenix on 16/9/5.
 *
 * 目前存在问题:
 * 1. 弹出框关闭后会导致一次异常
 * 2. 考虑angularjs,是否全部使用呢?
 */
@Controller
@RequestMapping(value = "/tools/console")
public class ConsoleController extends BaseController {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(ConsoleController.class);

    /**
     * The media type to send as the content type of stream contents if no
     * other media type is specified.
     */
    private static final String DEFAULT_MEDIA_TYPE = "application/octet-stream";

    // ================================================================
    // Fields
    // ================================================================

    @Autowired
    private ConsoleParamService consoleParamService;

    @Autowired
    private TunnelRequestService tunnelRequestService;

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * console配置界面
     */
    @RequestMapping(value = {"index", ""})
    public String index() {
        return "modules/tools/consoleParamIndex";
    }

    /**
     * console配置界面
     */
    @RequestMapping(value = {"console"})
    public String console(String id, Model model) {
        logger.info("connect id is {}", id);
        model.addAttribute("id", id);
        return "modules/tools/console";
    }

    /**
     * 下载界面,必须console的自路径
     */
    @RequestMapping(value = "console/tunnels/{tunnel}/streams/{index}/{filename}",
            method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    public void download(@PathVariable("tunnel") final String tunnel,
                                           @PathVariable("index") final int index,
                                           @PathVariable("filename") String filename,
                                           HttpServletResponse response) {

        StreamInterceptingTunnel guacamoleTunnel = tunnelRequestService.getTunnelById(tunnel);

        response.setHeader("content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");

        //处理中文文件名中文乱码问题
        String fileName = null;
        try {
            fileName = new String(Base64.decodeBase64(filename), "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            logger.error("", e);
        }
        response.setHeader("Content-Disposition", "attachment;filename="+fileName);

        if (guacamoleTunnel != null) {

            ByteArrayOutputStream output = new ByteArrayOutputStream();

            try {
                guacamoleTunnel.interceptStream(index, output);

                response.getOutputStream().write(output.toByteArray());
                output.close();
            } catch (Exception e) {
                logger.error("", e);
            }
        }
    }

    /**
     * 下载界面,必须console的自路径
     */
    @RequestMapping(value = "console/entity/tunnels/{tunnel}/streams/{index}/{filename}",
            method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
    public ResponseEntity<byte[]> downloadWithEntity(@PathVariable("tunnel") final String tunnel,
                                           @PathVariable("index") final int index,
                                           @PathVariable("filename") String filename) {

        StreamInterceptingTunnel guacamoleTunnel = tunnelRequestService.getTunnelById(tunnel);

        byte[] send = new byte[0];
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        //处理中文文件名中文乱码问题
        String fileName = null;
        try {
            fileName = new String(Base64.decodeBase64(filename), "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            logger.error("", e);
        }
        headers.setContentDispositionFormData("attachment", fileName);

        if (guacamoleTunnel != null) {

            ByteArrayOutputStream output = new ByteArrayOutputStream();

            try {
                guacamoleTunnel.interceptStream(index, output);

                return new ResponseEntity<>(output.toByteArray(), headers, HttpStatus.CREATED);
            } catch (GuacamoleException e) {
                logger.error("", e);
            }
        }

        return new ResponseEntity<>(send, headers, HttpStatus.CREATED);
    }

    /**
     * 下载界面,必须console的自路径
     */
    @RequestMapping(value = "console/tunnels/{tunnel}/streams/{index}/{filename}",
            method = RequestMethod.POST)
    public void upload(@PathVariable("tunnel") final String tunnel,
                       @PathVariable("index") final int index,
                       @PathVariable("filename") String filename,
                       @RequestParam("file") MultipartFile multipartFile) {

        StreamInterceptingTunnel guacamoleTunnel = tunnelRequestService.getTunnelById(tunnel);

        //处理中文文件名中文乱码问题
        String fileName = null;
        try {
            fileName = new String(Base64.decodeBase64(filename), "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            logger.error("", e);
        }
        logger.debug("upload file name is {} ", fileName);

        if (guacamoleTunnel != null) {

            try {
                // Send input over stream
                guacamoleTunnel.interceptStream(index, multipartFile.getInputStream());
            } catch (Exception e) {
                logger.error("", e);
            }
        }
    }

    /**
     * 参数列表页
     */
    @RequiresPermissions("tools:console:view")
    @RequestMapping(value = {"paramIndex"})
    public String paramIndex() {
        return "modules/tools/consoleParamIndex";
    }

    /**
     * 参数表单页
     */
    @RequiresPermissions("tools:console:view")
    @RequestMapping(value = "form")
    public String form(String id, String type, Model model) {
        if (StringUtils.isNotBlank(id) && !StringUtils.endsWithIgnoreCase("-999", id)) {
            //  获取指定的group和name的信息
            BasicConfiguration configuration = consoleParamService.findByConnId(id);
            model.addAttribute("entity", JsonMapper.toJsonString(configuration));
            model.addAttribute("type", configuration.getProtocol());
        } else {
            BasicConfiguration configuration = new BasicConfiguration();
            model.addAttribute("type", type);
            model.addAttribute("entity", JsonMapper.toJsonString(configuration));
        }
        return "modules/tools/consoleParamForm";
    }

    /**
     * 配置列表页
     */
    @RequiresPermissions("tools:console:view")
    @RequestMapping(value = "table")
    public String table(String id, String type, Model model) {
        model.addAttribute("id", id);
        model.addAttribute("type", type);
        return "modules/tools/consoleParamList";
    }

    /**
     * 获取参数列表
     */
    @RequiresPermissions("tools:console:view")
    @RequestMapping(value = "list")
    @ResponseBody
    public Page<BasicConfiguration> list(HttpServletRequest request) {
        Page<BasicConfiguration> page = new Page<>(request);
        // 查询扩展
        String extraSearch = page.getDatatablesCriterias().getExtraSearch();
        // 转换
        ConsoleParam consoleParam = JsonMapper.getInstance().fromJson(extraSearch, ConsoleParam.class);
        if (consoleParam == null) {
            consoleParam = new ConsoleParam();
        }
        page.setCount(1);
        page.setList(consoleParamService.findByType(consoleParam.getType()));

        return page;
    }

    /**
     * 获取参数树
     */
    @RequiresPermissions("user")
    @ResponseBody
    @RequestMapping(value = "treeData")
    public List<Map<String, Object>> treeData(String id) {
        List<Map<String, Object>> mapList = Lists.newArrayList();
        List<BasicConfiguration> list;
        if (StringUtils.isBlank(id)) {
            list = consoleParamService.findByType(null);
            // 设置一个隐藏节点,新增跳转时使用
            Map<String, Object> hiddenNode = Maps.newHashMap();
            hiddenNode.put("id", "-999");
            hiddenNode.put("name", "hidden");
            hiddenNode.put("type", ConsoleParam.TYPE_UNKNOWN);
            hiddenNode.put("isHidden", true);

            mapList.add(hiddenNode);

            // 添加根节点和类型节点
            mapList.add(buildNode(ConsoleParam.TYPE_ALL));
            mapList.add(buildNode(ConsoleParam.TYPE_SSH));
            mapList.add(buildNode(ConsoleParam.TYPE_TELNET));
            mapList.add(buildNode(ConsoleParam.TYPE_VNC));
            mapList.add(buildNode(ConsoleParam.TYPE_RDP));

        } else {
            list = consoleParamService.findByType(id);
            // 点击根节点,需要把固定节点添加一遍
            if (StringUtils.equals(id, ConsoleParam.TYPE_ALL)) {
                mapList.add(buildNode(ConsoleParam.TYPE_SSH));
                mapList.add(buildNode(ConsoleParam.TYPE_TELNET));
                mapList.add(buildNode(ConsoleParam.TYPE_VNC));
                mapList.add(buildNode(ConsoleParam.TYPE_RDP));
            }
        }

        list.stream().filter(e -> e.getHostname() != null)
                .forEach(e -> {
                    Map<String, Object> map = Maps.newHashMap();
                    map.put("id", e.getId());
                    map.put("pId", e.getProtocol());
                    map.put("name", e.getHostname());
                    map.put("type", e.getProtocol());
                    map.put("isParent", false);

                    mapList.add(map);
                });

        return mapList;
    }

    /**
     * 更新参数
     */
    @RequiresPermissions("tools:console:edit")
    @RequestMapping(value = "save")
    @ResponseBody
    public Result save(String type, String data) {
        Result result = new Result();
        if (MODE_DEMO) {
            result.setStatus(Result.ERROR);
            result.setMessage("演示模式，不允许操作!");
            return result;
        }
        BasicConfiguration configuration = null;
        if (StringUtils.isNotBlank(type) && StringUtils.isNotBlank(data)) {
            if (StringUtils.equals(ConsoleParam.TYPE_SSH, type)) {
                configuration = JsonMapper.getInstance().fromJson(StringEscapeUtils.unescapeHtml4(data), SSHConfiguration.class);
            } else if (StringUtils.equals(ConsoleParam.TYPE_TELNET, type)) {
                configuration = JsonMapper.getInstance().fromJson(StringEscapeUtils.unescapeHtml4(data), TelnetConfiguration.class);
            } else if (StringUtils.equals(ConsoleParam.TYPE_VNC, type)) {
                configuration = JsonMapper.getInstance().fromJson(StringEscapeUtils.unescapeHtml4(data), VNCConfiguration.class);
            } else if (StringUtils.equals(ConsoleParam.TYPE_RDP, type)) {
                configuration = JsonMapper.getInstance().fromJson(StringEscapeUtils.unescapeHtml4(data), RDPConfiguration.class);
            }
        }
        if (configuration != null) {
            if (StringUtils.isNotBlank(configuration.getId())) {
                consoleParamService.delete(configuration.getId());
            }
            configuration.setId(IdGen.uuid36());

            logger.debug("save configuration is {}", configuration);
            consoleParamService.save(configuration);
            result.setStatus(Result.SUCCESS);
            result.setMessage("保存配置参数'" + configuration.getProtocol() + "'成功");
        } else {
            result.setStatus(Result.ERROR);
            result.setMessage("保存配置参数失败,参数错误");
        }

        return result;
    }

    /**
     * 删除参数
     */
    @RequiresPermissions("tools:console:edit")
    @RequestMapping(value = "delete")
    @ResponseBody
    public Result delete(String id) {
        Result result = new Result();
        if (MODE_DEMO) {
            result.setStatus(Result.ERROR);
            result.setMessage("演示模式，不允许操作!");
            return result;
        }
        consoleParamService.delete(id);
        result.setStatus(Result.SUCCESS);
        result.setMessage("删除配置参数成功");
        return result;
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * 根据type构建固定节点
     */
    private Map<String, Object> buildNode(String type) {

        Map<String, Object> node = Maps.newHashMap();
        node.put("id", type);
        node.put("name", getLabel(type));
        node.put("type", type);
        node.put("isParent", true);
        if (StringUtils.equals(type, ConsoleParam.TYPE_ALL)) {
            node.put("pId", "0");
        } else {
            node.put("pId", ConsoleParam.TYPE_ALL);
        }

        return node;
    }

    /**
     * 根据type获取显示名
     */
    private String getLabel(String type) {

        if (StringUtils.equals(type, ConsoleParam.TYPE_SSH)) {
            return "SSH 协议";
        } else if (StringUtils.equals(type, ConsoleParam.TYPE_TELNET)) {
            return "TELNET 协议";
        } else if (StringUtils.equals(type, ConsoleParam.TYPE_VNC)) {
            return "VNC 协议";
        } else if (StringUtils.equals(type, ConsoleParam.TYPE_RDP)) {
            return "RDP 协议";
        }

        return "所有协议";
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
